package com.ocean.web.util;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * 单个时间网格内的九点线性插值工具
 * 功能：固定某一时间切片，用目标点周围3×3共9个空间点的加权平均做插值
 */
public class NinePointInterpolator9 {
    // 可配置参数
    private final List<Float> invalidValues; // 无效值列表
    private final float minReasonable;       // 合理下限
    private final float maxReasonable;       // 合理上限
    private final DecimalFormat dataFormat;  // 数值格式化

    // 空间点封装（经纬度+值+有效性+距离）
    static class SpatialPoint {
        float lat;         // 纬度
        float lon;         // 经度
        float value;       // 数值
        boolean isValid;   // 是否有效
        String invalidReason; // 无效原因
        float distance;    // 与目标点的距离（用于计算权重）

        SpatialPoint(float lat, float lon, float value, boolean isValid, String invalidReason, float distance) {
            this.lat = lat;
            this.lon = lon;
            this.value = value;
            this.isValid = isValid;
            this.invalidReason = invalidReason;
            this.distance = distance;
        }
    }

    // 插值结果封装
    static class InterpolationResult {
        int targetTimeIndex;       // 目标时间索引
        float targetTimeValue;     // 目标时间值
        float targetLat;           // 目标纬度
        float targetLon;           // 目标经度
        float interpolatedValue;   // 插值结果
        String strategy;           // 插值策略
        List<SpatialPoint> usedValidPoints; // 用到的有效点（含权重）
        List<SpatialPoint> usedInvalidPoints; // 用到的无效点

        InterpolationResult(int timeIndex, float timeValue, float lat, float lon, float result,
                            String strategy, List<SpatialPoint> valid, List<SpatialPoint> invalid) {
            this.targetTimeIndex = timeIndex;
            this.targetTimeValue = timeValue;
            this.targetLat = lat;
            this.targetLon = lon;
            this.interpolatedValue = result;
            this.strategy = strategy;
            this.usedValidPoints = valid;
            this.usedInvalidPoints = invalid;
        }
    }

    /**
     * 构造函数：初始化参数
     */
    public NinePointInterpolator9(List<Float> invalidValues, float minReasonable,
                                 float maxReasonable, String formatPattern) {
        this.invalidValues = invalidValues;
        this.minReasonable = minReasonable;
        this.maxReasonable = maxReasonable;
        this.dataFormat = new DecimalFormat(formatPattern);
    }

    /**
     * 核心方法：单个时间网格内的九点插值
     */
    public InterpolationResult interpolate(String ncPath, String dataVarName,
                                           int targetTimeIndex, float targetLat, float targetLon) throws IOException {
        NetcdfFile ncFile = null;
        try {
            ncFile = NetcdfFile.open(ncPath);

            // 1. 获取变量
            Variable timeVar = ncFile.findVariable("time");
            Variable latVar = ncFile.findVariable("lat");
            Variable lonVar = ncFile.findVariable("lon");
            Variable dataVar = ncFile.findVariable(dataVarName);

            if (timeVar == null || latVar == null || lonVar == null || dataVar == null) {
                throw new RuntimeException("缺少变量：time/lat/lon/" + dataVarName);
            }

            // 2. 读取基础数据
            float[] times = (float[]) timeVar.read().copyTo1DJavaArray();
            float[] lats = (float[]) latVar.read().copyTo1DJavaArray();
            float[] lons = (float[]) lonVar.read().copyTo1DJavaArray();

            // 校验时间索引
            if (targetTimeIndex < 0 || targetTimeIndex >= times.length) {
                throw new RuntimeException("时间索引无效（范围：0~" + (times.length - 1) + "）");
            }
            float targetTimeValue = times[targetTimeIndex];

            // 3. 提取目标时间切片的空间数据
            float[][][] data3D = (float[][][]) dataVar.read().copyToNDJavaArray();
            float[][] spatialData = data3D[targetTimeIndex];

            // 4. 找到目标点周围3×3的9个邻域点（核心：扩展为3个相邻索引）
            int[] latIndices = findThreeNearestIndices(targetLat, lats); // 纬度索引[i-1, i, i+1]
            int[] lonIndices = findThreeNearestIndices(targetLon, lons); // 经度索引[j-1, j, j+1]

            // 5. 收集9个点，计算与目标点的距离，并区分有效/无效
            List<SpatialPoint> allPoints = new ArrayList<>();
            for (int latIdx : latIndices) {
                for (int lonIdx : lonIndices) {
                    if (latIdx < 0 || latIdx >= lats.length || lonIdx < 0 || lonIdx >= lons.length) {
                        continue; // 跳过网格外的点（边缘情况）
                    }
                    float lat = lats[latIdx];
                    float lon = lons[lonIdx];
                    float value = spatialData[latIdx][lonIdx];
                    // 计算目标点与当前点的距离（简化为平面距离，实际可替换为球面距离）
                    float distance = calculateDistance(targetLat, targetLon, lat, lon);
                    // 判断有效性并记录原因
                    boolean isValid = true;
                    String invalidReason = "";
                    if (value != value) {
                        isValid = false;
                        invalidReason = "NaN（非数字）";
                    } else if (invalidValues.contains(value)) {
                        isValid = false;
                        invalidReason = value + "（用户定义无效值）";
                    } else if (value < minReasonable) {
                        isValid = false;
                        invalidReason = value + "（低于下限" + minReasonable + "）";
                    } else if (value > maxReasonable) {
                        isValid = false;
                        invalidReason = value + "（高于上限" + maxReasonable + "）";
                    }
                    allPoints.add(new SpatialPoint(lat, lon, value, isValid, invalidReason, distance));
                }
            }

            // 6. 分离有效点和无效点
            List<SpatialPoint> validPoints = new ArrayList<>();
            List<SpatialPoint> invalidPoints = new ArrayList<>();
            for (SpatialPoint p : allPoints) {
                if (p.isValid) {
                    validPoints.add(p);
                } else {
                    invalidPoints.add(p);
                }
            }

            // 7. 九点加权插值计算
            float resultValue = Float.NaN;
            String strategy = "";
            int validCount = validPoints.size();

            if (validCount == 0) {
                strategy = "9个邻域点均无效，无法插值";
            } else {
                // 计算权重：距离反比（距离越小，权重越大），避免距离为0的除零问题
                float totalWeight = 0f;
                float[] weights = new float[validCount];
                for (int i = 0; i < validCount; i++) {
                    SpatialPoint p = validPoints.get(i);
                    float weight = (p.distance < 1e-6) ? 1f : 1f / p.distance; // 距离为0时权重为1
                    weights[i] = weight;
                    totalWeight += weight;
                }
                // 归一化权重（确保总和为1）
                for (int i = 0; i < validCount; i++) {
                    weights[i] /= totalWeight;
                }
                // 加权求和
                resultValue = 0f;
                for (int i = 0; i < validCount; i++) {
                    resultValue += validPoints.get(i).value * weights[i];
                }
                // 记录策略（含有效点数量和权重信息）
                strategy = validCount + "个有效点，九点距离加权插值（权重和为1）";
                // 给有效点附加权重信息（方便打印）
                for (int i = 0; i < validCount; i++) {
                    validPoints.get(i).invalidReason = "权重：" + new DecimalFormat("0.000").format(weights[i]);
                }
            }

            return new InterpolationResult(
                    targetTimeIndex, targetTimeValue, targetLat, targetLon,
                    resultValue, strategy, validPoints, invalidPoints
            );

        } finally {
            if (ncFile != null) ncFile.close();
        }
    }

    /**
     * 查找目标值的3个相邻索引（用于3×3网格）
     * 若目标在边缘，返回[0,0,1]或[n-2,n-1,n-1]等（避免数组越界）
     */
    private int[] findThreeNearestIndices(float target, float[] array) {
        int n = array.length;
        if (n == 1) {
            return new int[]{0, 0, 0}; // 仅1个点时重复3次
        }
        // 先找到最近的单个索引
        int nearestIdx = findNearestSingleIndex(target, array);
        // 生成3个相邻索引（处理边界）
        int idx1 = Math.max(0, nearestIdx - 1);
        int idx2 = nearestIdx;
        int idx3 = Math.min(n - 1, nearestIdx + 1);
        return new int[]{idx1, idx2, idx3};
    }

    /**
     * 查找目标值的最近单个索引
     */
    private int findNearestSingleIndex(float target, float[] array) {
        int nearestIdx = 0;
        float minDiff = Math.abs(array[0] - target);
        for (int i = 1; i < array.length; i++) {
            float diff = Math.abs(array[i] - target);
            if (diff < minDiff) {
                minDiff = diff;
                nearestIdx = i;
            }
        }
        return nearestIdx;
    }

    /**
     * 计算两点之间的距离（简化为平面距离，单位：度）
     * 实际应用中可替换为球面距离公式（如Haversine公式）
     */
    private float calculateDistance(float lat1, float lon1, float lat2, float lon2) {
        float dLat = lat2 - lat1;
        float dLon = lon2 - lon1;
        return (float) Math.sqrt(dLat * dLat + dLon * dLon); // 欧氏距离
    }

    /**
     * 打印结果（仅插值汇总、有效点、无效点）
     */
    public void printResult(InterpolationResult result) {
        System.out.println("==================================== 插值汇总 ====================================");
        System.out.printf("目标时间索引：%d（时间值：%.2f）%n", result.targetTimeIndex, result.targetTimeValue);
        System.out.printf("目标经纬度：(%.4f°N, %.4f°E)%n", result.targetLat, result.targetLon);
        System.out.println("插值策略：" + result.strategy);
        if (!Float.isNaN(result.interpolatedValue)) {
            System.out.println("九点插值结果：" + dataFormat.format(result.interpolatedValue));
        } else {
            System.out.println("九点插值结果：无法计算");
        }

        System.out.println("\n==================================== 插值用到的有效点（含权重） ====================================");
        if (result.usedValidPoints.isEmpty()) {
            System.out.println("无有效点");
        } else {
            for (SpatialPoint p : result.usedValidPoints) {
                System.out.printf("经纬度：(%.4f°N, %.4f°E) → 数值：%s → %s%n",
                        p.lat, p.lon, dataFormat.format(p.value), p.invalidReason);
            }
        }

        System.out.println("\n==================================== 插值用到的无效点 ====================================");
        if (result.usedInvalidPoints.isEmpty()) {
            System.out.println("无无效点");
        } else {
            for (SpatialPoint p : result.usedInvalidPoints) {
                System.out.printf("经纬度：(%.4f°N, %.4f°E) → 数值：%s → 无效原因：%s%n",
                        p.lat, p.lon, dataFormat.format(p.value), p.invalidReason);
            }
        }
        System.out.println("======================================================================================");
    }

    // 测试主方法
    public static void main(String[] args) {
        try {
            // 配置参数（根据数据类型调整）
            List<Float> invalidValues = List.of(-9999f, -999f);
            float minReasonable = -2f;
            float maxReasonable = 40f;
            NinePointInterpolator9 interpolator = new NinePointInterpolator9(
                    invalidValues, minReasonable, maxReasonable, "0.0000"
            );

            // 目标参数
            String ncPath = "E:\\NMF_BEN_GO_GLDT_2025090900_168h_SST_cropped.nc";
            String dataVarName = "sst";
            int targetTimeIndex = 0; // 固定时间切片
            float targetLat = 40.3f;
            float targetLon = 122.1f;

            // 执行插值并打印
            InterpolationResult result = interpolator.interpolate(ncPath, dataVarName, targetTimeIndex, targetLat, targetLon);
            interpolator.printResult(result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}